4
4
.
.
2
2
C
C
o
o
n
n
t
t
a
a
i
i
n
n
e
e
r
r
V
V
i
i
e
e
w
w
s
s
I
I
n
n
f
f
o
o
Container Views are used to put other Views inside them to be able to easier organize them and navigate through them.
C
C
o
o
n
n
t
t
a
a
i
i
n
n
e
e
r
r
V
V
i
i
e
e
w
w
T
T
y
y
p
p
e
e
s
s
HStack, VStack and Spacer Views organize child Views horizontally or vertically with some spaces between them.
Navigation View has an initial child View and as you go to other Views you get a Back Button to go back to previous Views.
HStack with Spacers VStack with Spacers
Navigation View: Initial View Navigation View: Second View
List
C
C
o
o
n
n
t
t
a
a
i
i
n
n
e
e
r
r
V
V
i
i
e
e
w
w
[
[
R
R
]
]
Container View (like VStack) is just a struct whose Constructor accepts
two input parameters with default values (so you can omit them inside parameter list ())
Trailing Closure as last parameter (so it can be given outside parameter list () inside {} brackets)
Provided Closure doesn't return any value. Instead it will be used as a Builder Function.
For Builder Function return value is calculated for each line and these are then combined into what function returns.
In the case of VStack each line returns a single View which are then added to a TupleView which is returned.
In the below example content Closure returns TupleView<Text, Text>.
Simplified code
struct ContentView: View {
var body : some View {
VStack {
Text("Hello")
Text("world")
}
}
}
E
E
x
x
p
p
l
l
i
i
c
c
i
i
t
t
c
c
a
a
l
l
l
l
t
t
o
o
V
V
S
S
t
t
a
a
c
c
k
k
In the above code body is computed Property with just a getter.
Body of the getter only has one line (call to VStack) in which case return keyword can be omitted.
By explicitly specifying getter and return the above code can be rewritten as follows.
Explicitly specifying getter and return
struct ContentView: View {
var body: some View {
get {
return VStack {
Text("Hello")
Text("world")
}
}
}
}
If you Right Click on VStack and select Jump to definition you will see that VStack Constructor is declared as
accepting 2 Input Parameters with default values (which can therefore be omitted)
Closure as last parameter (which can therefore be declared outside the parameter list)
VStack Constructor
struct VStack<Content> : View where Content : View {
init(alignment: HorizontalAlignment = .center, spacing: CGFloat? = nil, @ViewBuilder content: () ->
Content)
This means that our code can be rewritten as:
Calling VStack Constructor using parameter list
struct ContentView: View {
var body: some View {
get {
return VStack(alignment: .center, spacing: nil, content: { Text("Hello"); Text("world") })
}
}
}
R
R
e
e
m
m
o
o
v
v
i
i
n
n
g
g
F
F
u
u
n
n
c
c
t
t
i
i
o
o
n
n
B
B
u
u
i
i
l
l
d
d
e
e
r
r
From the Constructor declaration we see that last parameter is used as input into Function Builder @ViewBuilder.
In other words closure lines are used as input parameters into Method ViewBuilder.buildBlock().
This allows us to rewrite our code as: (we have omitted default parameters and provided Closure outside Parameter list)
Direct call to ViewBuilder.buildBlock()
struct ContentView: View {
var body: some View {
return VStack {
return ViewBuilder.buildBlock(Text("Hello"), Text("world!"))
}
}
}
If you Right Click on buildBlock and select Jump to definition you will see that buildBlock<C0, C1> is Generic Method
that accepts 2 Parameters (which must conform to View Protocol)
and returns TupleView<T> that contains Tuple with these two Views TupleView<(C0, C1)>
Declaration of buildBlock()
public static func buildBlock<C0, C1> (_ c0: C0, _ c1: C1) -> TupleView<(C0, C1)> where C0 : View, C1 : View
If you Right Click on TupleView and select Jump to definition you will see that TupleView is a simple Generic struct with a
single Property. And in our case this will be a Tuple containing our two Views (C0, C1).
Declaration of TupleView<T>
@frozen public struct TupleView<T> : View {
public var value: T
}
Since content Closure simply returns TupleView with our two Views inside the Tuple, we can now provide TupleView
directly to the VStack content parameter as: (So VStack's content Parameter contains Tuple with all of its children)
Provide TupleView to VStack content parameter
struct ContentView: View {
var body : some View {
return VStack {
TupleView( (Text("Hello"), Text("world!")) )
}
}
}
R
R
e
e
m
m
o
o
v
v
i
i
n
n
g
g
s
s
o
o
m
m
e
e
V
V
i
i
e
e
w
w
From the VStack Constructor we see that the full Data Type is VStack<Content> where Content is Generic Type.
And Content is defined as what is provided to third content Parameter and we have seen it is TupleView<(Text,Text)>.
This means that VStack full Data Type is VStack<TupleView<(Text,Text)>> which can be used to replace some View.
Without some View
struct ContentView: View {
var body: VStack<TupleView<(Text,Text)>> {
return VStack {
TupleView( (Text("Hello"), Text("world!")) )
}
}
}
The problem is that if we now change content of VStack, for instance by replacing one Text with Image, we would also
need to update what is returned by body Computed Property.
Changing VStack children
struct ContentView: View {
var body: VStack<TupleView<(Text,Image)>> {
return VStack {
TupleView( (Text("Hello"), Image(systemName: "star.circle.fill")) )
}
}
}
This is why we use some View, because with some View we can freely change content of the VStack without having to
manually update body's return type
manually specify exact body return type
define VStack Hierarchy twice (once inside VStack and then again as body's return type)
some View simply makes sure that body's getter always returns the same specific Type that conforms to View Protocol
without having to explicitly specify the Type VStack< TupleView<(Text,Image)> >